package com.oil.web.core.config;

import cn.hutool.json.JSONUtil;
import com.oil.common.utils.ServletUtils;
import com.oil.common.utils.ip.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

@Aspect
@Component
@Slf4j
public class LoggerAspect {

    private final ThreadLocal<Long> spent = new ThreadLocal<>();

    // 获取方法所在类的信息
    private static String getMethodName() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        return "[" + request.getRequestURI() + "]";
    }

    // 执行 joinPoint.proceed() 并统一出现异常时的返回格式
    public static Object proceed(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }

    /**
     * @author : LVQIU
     * @tag : print
     * @return : java.lang.String
     * @description :  日志量太大了就不打印了
     * @date : 2022/3/26 16:03
     */
    public static String print(Object result) {
        if(result == null) {
            return null;
        }
        if (result instanceof ResponseEntity) {
            // 用于以流的方式传输，比如文件下载
            ResponseEntity<?> responseEntity = (ResponseEntity<?>) result;
            return Objects.requireNonNull(responseEntity.getBody()).toString();
        }
        String resultstr = JSONUtil.toJsonStr(result);
        if (resultstr.length() > 3000) {
            resultstr = resultstr.substring(0, 3000).concat("...");
        }
        return resultstr;
    }

    // 切入点 controller 包下的全部类的全部方法
    @Pointcut("execution(* com.oil.business.controller.*.*(..))") // && !execution(* com.unionpay.controller.MarketRoutingController.*(..))
    public void controllerAspect() {

    }

    @Before("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) {
        spent.set(System.currentTimeMillis());
        StringBuilder param = new StringBuilder();
        Object[] args = joinPoint.getArgs();
        if (args != null) {
            int len = args.length;
            for (int i = 1; i <= len; i++) {
                Object arg = args[i - 1];
                param.append(i);
                param.append(": ");
                if (arg instanceof HttpServletRequest) {
                    param.append("HttpServletRequest...");
                } else if (arg instanceof HttpServletResponse) {
                    param.append("HttpServletResponse...");
                } else {
                    param.append(JSONUtil.toJsonStr(arg));
                }
                param.append(", ");
            }
        }
        log.info("\n");
        log.info("############################ REQUEST INFO START ############################");
        log.info("##### EXECUTED: [{}] {} .", ServletUtils.getRequest().getMethod(), getMethodName());
        log.info("##### IP:  {}", IpUtils.getIpAddr(ServletUtils.getRequest()));
        log.info("##### INPUT:  {}", param);
    }

    // 方法执行完毕，正常退出后被调用，打印所执行方法的全路径和返回结果
    @AfterReturning(value = "controllerAspect()", returning = "result")
    public void doAfterReturning(Object result) {
        // 处理完请求，返回内容
        log.info("##### OUTPUT:  {}", print(result));
        log.info("##### SPENT:  {}ms", (System.currentTimeMillis() - spent.get()));
        log.info("############################ REQUEST INFO END ##############################");
        log.info("\n");
    }

    // 方法执行完毕或者执行过程中抛出异常时被调用（必须是抛出，而不是被 try catch 吃掉），打印所执行方法的全路径和返回结果以及异常信息
    @AfterThrowing(value = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        log.error(getMethodName(), e);
    }

    // 环绕通知，代理所被切入的方法的执行，其内部的 joinPoint.proceed() 便是代理各个方法的执行，此处被 try catch 包裹。
    // 所有异常都在改方法内消化，无论是否出现异常，都将进入 @AfterReturning 方法，统一出现异常后的返回结果
    @Around("controllerAspect()")
    public Object doResponseBody(ProceedingJoinPoint joinPoint) throws Throwable {
        return proceed(joinPoint);
    }
}
